{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<center>\n",
    "    <p style=\"text-align:center\">\n",
    "        <img alt=\"phoenix logo\" src=\"https://storage.googleapis.com/arize-phoenix-assets/assets/phoenix-logo-light.svg\" width=\"200\"/>\n",
    "        <br>\n",
    "        <a href=\"https://arize.com/docs/phoenix/\">Docs</a>\n",
    "        |\n",
    "        <a href=\"https://github.com/Arize-ai/phoenix\">GitHub</a>\n",
    "        |\n",
    "        <a href=\"https://arize-ai.slack.com/join/shared_invite/zt-2w57bhem8-hq24MB6u7yE_ZF_ilOYSBw#/shared-invite/email\">Community</a>\n",
    "    </p>\n",
    "</center>\n",
    "<h1 align=\"center\">Code Readability Evals</h1>\n",
    "\n",
    "Arize provides tooling to evaluate LLM applications, including tools to determine the readability or unreadability of code generated by LLM applications.\n",
    "\n",
    "The purpose of this notebook is:\n",
    "\n",
    "- to evaluate the performance of an LLM-assisted approach to classifying\n",
    "  generated code as readable or unreadable using datasets with ground-truth\n",
    "  labels\n",
    "- to provide an experimental framework for users to iterate and improve on the default classification template.\n",
    "\n",
    "## Install Dependencies and Import Libraries"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "#####################\n",
    "## N_EVAL_SAMPLE_SIZE\n",
    "#####################\n",
    "# Eval sample size determines the run time\n",
    "# 100 samples: GPT-4 ~ 80 sec / GPT-3.5 ~ 40 sec\n",
    "# 1,000 samples: GPT-4 ~15-17 min / GPT-3.5 ~ 6-7min (depending on retries)\n",
    "# 10,000 samples GPT-4 ~170 min / GPT-3.5 ~ 70min\n",
    "N_EVAL_SAMPLE_SIZE = 10"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[31mERROR: Could not find a version that satisfies the requirement ip (from versions: none)\u001b[0m\u001b[31m\n",
      "\u001b[0m\u001b[31mERROR: No matching distribution found for ip\u001b[0m\u001b[31m\n",
      "\u001b[0m"
     ]
    }
   ],
   "source": [
    "!pip install -qq \"arize-phoenix-evals>=0.0.5\" \"openai>=1\" ipython matplotlib pycm scikit-learn tiktoken nest_asyncio 'httpx<0.28'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "ℹ️ To enable async request submission in notebook environments like Jupyter or Google Colab, optionally use `nest_asyncio`. `nest_asyncio` globally patches `asyncio` to enable event loops to be re-entrant. This is not required for non-notebook environments.\n",
    "\n",
    "Without `nest_asyncio`, eval submission can be much slower, depending on your organization's rate limits. Speed increases of about 5x are typical."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "import nest_asyncio\n",
    "\n",
    "nest_asyncio.apply()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "from getpass import getpass\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import openai\n",
    "import pandas as pd\n",
    "from pycm import ConfusionMatrix\n",
    "from sklearn.metrics import classification_report\n",
    "\n",
    "from phoenix.evals import (\n",
    "    CODE_READABILITY_PROMPT_RAILS_MAP,\n",
    "    CODE_READABILITY_PROMPT_TEMPLATE,\n",
    "    OpenAIModel,\n",
    "    download_benchmark_dataset,\n",
    "    llm_classify,\n",
    ")\n",
    "\n",
    "pd.set_option(\"display.max_colwidth\", None)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Download Benchmark Dataset\n",
    "\n",
    "We'll evaluate the evaluation system consisting of an LLM model and settings in\n",
    "addition to an evaluation prompt template against a benchmark datasets of\n",
    "readable and unreadable code with ground-truth labels. Currently supported\n",
    "datasets for this task include:\n",
    "\n",
    "- openai_humaneval_with_readability"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Unnamed: 0</th>\n",
       "      <th>task_id</th>\n",
       "      <th>prompt</th>\n",
       "      <th>canonical_solution</th>\n",
       "      <th>test</th>\n",
       "      <th>entry_point</th>\n",
       "      <th>readable</th>\n",
       "      <th>solution</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>0</td>\n",
       "      <td>HumanEval/0</td>\n",
       "      <td>from typing import List\\n\\n\\ndef has_close_elements(numbers: List[float], threshold: float) -&gt; bool:\\n    \"\"\" Check if in given list of numbers, are any two numbers closer to each other than\\n    given threshold.\\n    &gt;&gt;&gt; has_close_elements([1.0, 2.0, 3.0], 0.5)\\n    False\\n    &gt;&gt;&gt; has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3)\\n    True\\n    \"\"\"\\n</td>\n",
       "      <td>for idx, elem in enumerate(numbers):\\n        for idx2, elem2 in enumerate(numbers):\\n            if idx != idx2:\\n                distance = abs(elem - elem2)\\n                if distance &lt; threshold:\\n                    return True\\n\\n    return False\\n</td>\n",
       "      <td>\\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate([1.0, 2.0, 3.9, 4.0, 5.0, 2.2], 0.3) == True\\n    assert candidate([1.0, 2.0, 3.9, 4.0, 5.0, 2.2], 0.05) == False\\n    assert candidate([1.0, 2.0, 5.9, 4.0, 5.0], 0.95) == True\\n    assert candidate([1.0, 2.0, 5.9, 4.0, 5.0], 0.8) == False\\n    assert candidate([1.0, 2.0, 3.0, 4.0, 5.0, 2.0], 0.1) == True\\n    assert candidate([1.1, 2.2, 3.1, 4.1, 5.1], 1.0) == True\\n    assert candidate([1.1, 2.2, 3.1, 4.1, 5.1], 0.5) == False\\n\\n</td>\n",
       "      <td>has_close_elements</td>\n",
       "      <td>True</td>\n",
       "      <td>for idx, elem in enumerate(numbers):\\n        for idx2, elem2 in enumerate(numbers):\\n            if idx != idx2:\\n                distance = abs(elem - elem2)\\n                if distance &lt; threshold:\\n                    return True\\n\\n    return False\\n</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>1</td>\n",
       "      <td>HumanEval/1</td>\n",
       "      <td>from typing import List\\n\\n\\ndef separate_paren_groups(paren_string: str) -&gt; List[str]:\\n    \"\"\" Input to this function is a string containing multiple groups of nested parentheses. Your goal is to\\n    separate those group into separate strings and return the list of those.\\n    Separate groups are balanced (each open brace is properly closed) and not nested within each other\\n    Ignore any spaces in the input string.\\n    &gt;&gt;&gt; separate_paren_groups('( ) (( )) (( )( ))')\\n    ['()', '(())', '(()())']\\n    \"\"\"\\n</td>\n",
       "      <td>result = []\\n    current_string = []\\n    current_depth = 0\\n\\n    for c in paren_string:\\n        if c == '(':\\n            current_depth += 1\\n            current_string.append(c)\\n        elif c == ')':\\n            current_depth -= 1\\n            current_string.append(c)\\n\\n            if current_depth == 0:\\n                result.append(''.join(current_string))\\n                current_string.clear()\\n\\n    return result\\n</td>\n",
       "      <td>\\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate('(()()) ((())) () ((())()())') == [\\n        '(()())', '((()))', '()', '((())()())'\\n    ]\\n    assert candidate('() (()) ((())) (((())))') == [\\n        '()', '(())', '((()))', '(((())))'\\n    ]\\n    assert candidate('(()(())((())))') == [\\n        '(()(())((())))'\\n    ]\\n    assert candidate('( ) (( )) (( )( ))') == ['()', '(())', '(()())']\\n</td>\n",
       "      <td>separate_paren_groups</td>\n",
       "      <td>True</td>\n",
       "      <td>result = []\\n    current_string = []\\n    current_depth = 0\\n\\n    for c in paren_string:\\n        if c == '(':\\n            current_depth += 1\\n            current_string.append(c)\\n        elif c == ')':\\n            current_depth -= 1\\n            current_string.append(c)\\n\\n            if current_depth == 0:\\n                result.append(''.join(current_string))\\n                current_string.clear()\\n\\n    return result\\n</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>2</td>\n",
       "      <td>HumanEval/2</td>\n",
       "      <td>\\n\\ndef truncate_number(number: float) -&gt; float:\\n    \"\"\" Given a positive floating point number, it can be decomposed into\\n    and integer part (largest integer smaller than given number) and decimals\\n    (leftover part always smaller than 1).\\n\\n    Return the decimal part of the number.\\n    &gt;&gt;&gt; truncate_number(3.5)\\n    0.5\\n    \"\"\"\\n</td>\n",
       "      <td>return number % 1.0\\n</td>\n",
       "      <td>\\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate(3.5) == 0.5\\n    assert abs(candidate(1.33) - 0.33) &lt; 1e-6\\n    assert abs(candidate(123.456) - 0.456) &lt; 1e-6\\n</td>\n",
       "      <td>truncate_number</td>\n",
       "      <td>False</td>\n",
       "      <td>return((lambda x: (lambda y: y(x))(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda y: f(lambda u: y(y)(u)))))(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda y: f(lambda u: y(y)(u))))(lambda f: lambda x: x if x == 0 else f(x - 1) + 1)(number % 1.0))</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>3</td>\n",
       "      <td>HumanEval/3</td>\n",
       "      <td>from typing import List\\n\\n\\ndef below_zero(operations: List[int]) -&gt; bool:\\n    \"\"\" You're given a list of deposit and withdrawal operations on a bank account that starts with\\n    zero balance. Your task is to detect if at any point the balance of account fallls below zero, and\\n    at that point function should return True. Otherwise it should return False.\\n    &gt;&gt;&gt; below_zero([1, 2, 3])\\n    False\\n    &gt;&gt;&gt; below_zero([1, 2, -4, 5])\\n    True\\n    \"\"\"\\n</td>\n",
       "      <td>balance = 0\\n\\n    for op in operations:\\n        balance += op\\n        if balance &lt; 0:\\n            return True\\n\\n    return False\\n</td>\n",
       "      <td>\\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate([]) == False\\n    assert candidate([1, 2, -3, 1, 2, -3]) == False\\n    assert candidate([1, 2, -4, 5, 6]) == True\\n    assert candidate([1, -1, 2, -2, 5, -5, 4, -4]) == False\\n    assert candidate([1, -1, 2, -2, 5, -5, 4, -5]) == True\\n    assert candidate([1, -2, 2, -2, 5, -5, 4, -4]) == True\\n</td>\n",
       "      <td>below_zero</td>\n",
       "      <td>True</td>\n",
       "      <td>balance = 0\\n\\n    for op in operations:\\n        balance += op\\n        if balance &lt; 0:\\n            return True\\n\\n    return False\\n</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>4</td>\n",
       "      <td>HumanEval/4</td>\n",
       "      <td>from typing import List\\n\\n\\ndef mean_absolute_deviation(numbers: List[float]) -&gt; float:\\n    \"\"\" For a given list of input numbers, calculate Mean Absolute Deviation\\n    around the mean of this dataset.\\n    Mean Absolute Deviation is the average absolute difference between each\\n    element and a centerpoint (mean in this case):\\n    MAD = average | x - x_mean |\\n    &gt;&gt;&gt; mean_absolute_deviation([1.0, 2.0, 3.0, 4.0])\\n    1.0\\n    \"\"\"\\n</td>\n",
       "      <td>mean = sum(numbers) / len(numbers)\\n    return sum(abs(x - mean) for x in numbers) / len(numbers)\\n</td>\n",
       "      <td>\\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert abs(candidate([1.0, 2.0, 3.0]) - 2.0/3.0) &lt; 1e-6\\n    assert abs(candidate([1.0, 2.0, 3.0, 4.0]) - 1.0) &lt; 1e-6\\n    assert abs(candidate([1.0, 2.0, 3.0, 4.0, 5.0]) - 6.0/5.0) &lt; 1e-6\\n\\n</td>\n",
       "      <td>mean_absolute_deviation</td>\n",
       "      <td>True</td>\n",
       "      <td>mean = sum(numbers) / len(numbers)\\n    return sum(abs(x - mean) for x in numbers) / len(numbers)\\n</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Unnamed: 0      task_id  \\\n",
       "0           0  HumanEval/0   \n",
       "1           1  HumanEval/1   \n",
       "2           2  HumanEval/2   \n",
       "3           3  HumanEval/3   \n",
       "4           4  HumanEval/4   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  prompt  \\\n",
       "0                                                                                                                                                                from typing import List\\n\\n\\ndef has_close_elements(numbers: List[float], threshold: float) -> bool:\\n    \"\"\" Check if in given list of numbers, are any two numbers closer to each other than\\n    given threshold.\\n    >>> has_close_elements([1.0, 2.0, 3.0], 0.5)\\n    False\\n    >>> has_close_elements([1.0, 2.8, 3.0, 4.0, 5.0, 2.0], 0.3)\\n    True\\n    \"\"\"\\n   \n",
       "1  from typing import List\\n\\n\\ndef separate_paren_groups(paren_string: str) -> List[str]:\\n    \"\"\" Input to this function is a string containing multiple groups of nested parentheses. Your goal is to\\n    separate those group into separate strings and return the list of those.\\n    Separate groups are balanced (each open brace is properly closed) and not nested within each other\\n    Ignore any spaces in the input string.\\n    >>> separate_paren_groups('( ) (( )) (( )( ))')\\n    ['()', '(())', '(()())']\\n    \"\"\"\\n   \n",
       "2                                                                                                                                                                                 \\n\\ndef truncate_number(number: float) -> float:\\n    \"\"\" Given a positive floating point number, it can be decomposed into\\n    and integer part (largest integer smaller than given number) and decimals\\n    (leftover part always smaller than 1).\\n\\n    Return the decimal part of the number.\\n    >>> truncate_number(3.5)\\n    0.5\\n    \"\"\"\\n   \n",
       "3                                                           from typing import List\\n\\n\\ndef below_zero(operations: List[int]) -> bool:\\n    \"\"\" You're given a list of deposit and withdrawal operations on a bank account that starts with\\n    zero balance. Your task is to detect if at any point the balance of account fallls below zero, and\\n    at that point function should return True. Otherwise it should return False.\\n    >>> below_zero([1, 2, 3])\\n    False\\n    >>> below_zero([1, 2, -4, 5])\\n    True\\n    \"\"\"\\n   \n",
       "4                                                                             from typing import List\\n\\n\\ndef mean_absolute_deviation(numbers: List[float]) -> float:\\n    \"\"\" For a given list of input numbers, calculate Mean Absolute Deviation\\n    around the mean of this dataset.\\n    Mean Absolute Deviation is the average absolute difference between each\\n    element and a centerpoint (mean in this case):\\n    MAD = average | x - x_mean |\\n    >>> mean_absolute_deviation([1.0, 2.0, 3.0, 4.0])\\n    1.0\\n    \"\"\"\\n   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                     canonical_solution  \\\n",
       "0                                                                                                                                                                                      for idx, elem in enumerate(numbers):\\n        for idx2, elem2 in enumerate(numbers):\\n            if idx != idx2:\\n                distance = abs(elem - elem2)\\n                if distance < threshold:\\n                    return True\\n\\n    return False\\n   \n",
       "1      result = []\\n    current_string = []\\n    current_depth = 0\\n\\n    for c in paren_string:\\n        if c == '(':\\n            current_depth += 1\\n            current_string.append(c)\\n        elif c == ')':\\n            current_depth -= 1\\n            current_string.append(c)\\n\\n            if current_depth == 0:\\n                result.append(''.join(current_string))\\n                current_string.clear()\\n\\n    return result\\n   \n",
       "2                                                                                                                                                                                                                                                                                                                                                                                                                                 return number % 1.0\\n   \n",
       "3                                                                                                                                                                                                                                                                                                               balance = 0\\n\\n    for op in operations:\\n        balance += op\\n        if balance < 0:\\n            return True\\n\\n    return False\\n   \n",
       "4                                                                                                                                                                                                                                                                                                                                                   mean = sum(numbers) / len(numbers)\\n    return sum(abs(x - mean) for x in numbers) / len(numbers)\\n   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   test  \\\n",
       "0  \\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate([1.0, 2.0, 3.9, 4.0, 5.0, 2.2], 0.3) == True\\n    assert candidate([1.0, 2.0, 3.9, 4.0, 5.0, 2.2], 0.05) == False\\n    assert candidate([1.0, 2.0, 5.9, 4.0, 5.0], 0.95) == True\\n    assert candidate([1.0, 2.0, 5.9, 4.0, 5.0], 0.8) == False\\n    assert candidate([1.0, 2.0, 3.0, 4.0, 5.0, 2.0], 0.1) == True\\n    assert candidate([1.1, 2.2, 3.1, 4.1, 5.1], 1.0) == True\\n    assert candidate([1.1, 2.2, 3.1, 4.1, 5.1], 0.5) == False\\n\\n   \n",
       "1                                                                                          \\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate('(()()) ((())) () ((())()())') == [\\n        '(()())', '((()))', '()', '((())()())'\\n    ]\\n    assert candidate('() (()) ((())) (((())))') == [\\n        '()', '(())', '((()))', '(((())))'\\n    ]\\n    assert candidate('(()(())((())))') == [\\n        '(()(())((())))'\\n    ]\\n    assert candidate('( ) (( )) (( )( ))') == ['()', '(())', '(()())']\\n   \n",
       "2                                                                                                                                                                                                                                                                                                                                      \\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate(3.5) == 0.5\\n    assert abs(candidate(1.33) - 0.33) < 1e-6\\n    assert abs(candidate(123.456) - 0.456) < 1e-6\\n   \n",
       "3                                                                                                                                             \\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert candidate([]) == False\\n    assert candidate([1, 2, -3, 1, 2, -3]) == False\\n    assert candidate([1, 2, -4, 5, 6]) == True\\n    assert candidate([1, -1, 2, -2, 5, -5, 4, -4]) == False\\n    assert candidate([1, -1, 2, -2, 5, -5, 4, -5]) == True\\n    assert candidate([1, -2, 2, -2, 5, -5, 4, -4]) == True\\n   \n",
       "4                                                                                                                                                                                                                                                                      \\n\\nMETADATA = {\\n    'author': 'jt',\\n    'dataset': 'test'\\n}\\n\\n\\ndef check(candidate):\\n    assert abs(candidate([1.0, 2.0, 3.0]) - 2.0/3.0) < 1e-6\\n    assert abs(candidate([1.0, 2.0, 3.0, 4.0]) - 1.0) < 1e-6\\n    assert abs(candidate([1.0, 2.0, 3.0, 4.0, 5.0]) - 6.0/5.0) < 1e-6\\n\\n   \n",
       "\n",
       "               entry_point  readable  \\\n",
       "0       has_close_elements      True   \n",
       "1    separate_paren_groups      True   \n",
       "2          truncate_number     False   \n",
       "3               below_zero      True   \n",
       "4  mean_absolute_deviation      True   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                               solution  \n",
       "0                                                                                                                                                                                      for idx, elem in enumerate(numbers):\\n        for idx2, elem2 in enumerate(numbers):\\n            if idx != idx2:\\n                distance = abs(elem - elem2)\\n                if distance < threshold:\\n                    return True\\n\\n    return False\\n  \n",
       "1      result = []\\n    current_string = []\\n    current_depth = 0\\n\\n    for c in paren_string:\\n        if c == '(':\\n            current_depth += 1\\n            current_string.append(c)\\n        elif c == ')':\\n            current_depth -= 1\\n            current_string.append(c)\\n\\n            if current_depth == 0:\\n                result.append(''.join(current_string))\\n                current_string.clear()\\n\\n    return result\\n  \n",
       "2                                                                                                                                                                                         return((lambda x: (lambda y: y(x))(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda y: f(lambda u: y(y)(u)))))(lambda f: (lambda x: f(lambda v: x(x)(v)))(lambda y: f(lambda u: y(y)(u))))(lambda f: lambda x: x if x == 0 else f(x - 1) + 1)(number % 1.0))  \n",
       "3                                                                                                                                                                                                                                                                                                               balance = 0\\n\\n    for op in operations:\\n        balance += op\\n        if balance < 0:\\n            return True\\n\\n    return False\\n  \n",
       "4                                                                                                                                                                                                                                                                                                                                                   mean = sum(numbers) / len(numbers)\\n    return sum(abs(x - mean) for x in numbers) / len(numbers)\\n  "
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dataset_name = \"openai_humaneval_with_readability\"\n",
    "df = download_benchmark_dataset(task=\"code-readability-classification\", dataset_name=dataset_name)\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Display Binary Readability Classification Template\n",
    "\n",
    "View the default template used to classify readability. You can tweak this template and evaluate its performance relative to the default."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "You are a stern but practical senior software engineer who cares a lot about simplicity and\n",
      "readability of code. Can you review the following code that was written by another engineer?\n",
      "Focus on readability of the code. Respond with \"readable\" if you think the code is readable,\n",
      "or \"unreadable\" if the code is unreadable or needlessly complex for what it's trying\n",
      "to accomplish.\n",
      "\n",
      "ONLY respond with \"readable\" or \"unreadable\"\n",
      "\n",
      "Task Assignment:\n",
      "```\n",
      "{input}\n",
      "```\n",
      "\n",
      "Implementation to Evaluate:\n",
      "```\n",
      "{output}\n",
      "```\n",
      "\n"
     ]
    }
   ],
   "source": [
    "print(CODE_READABILITY_PROMPT_TEMPLATE)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The template variables are:\n",
    "\n",
    "- **input:** the query from the user describing the coding task\n",
    "- **output:** an implementation of the coding task"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configure the LLM\n",
    "\n",
    "Configure your OpenAI API key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "if not (openai_api_key := os.getenv(\"OPENAI_API_KEY\")):\n",
    "    openai_api_key = getpass(\"🔑 Enter your OpenAI API key: \")\n",
    "openai.api_key = openai_api_key\n",
    "os.environ[\"OPENAI_API_KEY\"] = openai_api_key"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Instantiate the LLM and set parameters."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Benchmark Dataset Sample\n",
    "Sample size determines run time\n",
    "Recommend iterating small: 100 samples\n",
    "Then increasing to large test set"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = df.sample(n=N_EVAL_SAMPLE_SIZE).reset_index(drop=True)\n",
    "df = df.rename(\n",
    "    columns={\"prompt\": \"input\", \"solution\": \"output\"},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## LLM Evals: Code Readability Classifications GPT-4\n",
    "\n",
    "Run readability classifications against a subset of the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = OpenAIModel(\n",
    "    model=\"gpt-4\",\n",
    "    temperature=0.0,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"Hello! I'm working perfectly. How can I assist you today?\""
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "model(\"Hello world, this is a test if you are working?\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1a7c0896c4344e95b8df09aa049724fe",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "llm_classify |          | 0/10 (0.0%) | ⏳ 00:00<? | ?it/s"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# The rails is used to hold the output to specific values based on the template\n",
    "# It will remove text such as \",,,\" or \"...\"\n",
    "# Will ensure the binary value expected from the template is returned\n",
    "rails = list(CODE_READABILITY_PROMPT_RAILS_MAP.values())\n",
    "readability_classifications = llm_classify(\n",
    "    dataframe=df,\n",
    "    template=CODE_READABILITY_PROMPT_TEMPLATE,\n",
    "    model=model,\n",
    "    rails=rails,\n",
    "    concurrency=20,\n",
    ")[\"label\"].tolist()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "Evaluate the predictions against human-labeled ground-truth readability labels."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "              precision    recall  f1-score   support\n",
      "\n",
      "    readable       0.83      0.83      0.83         6\n",
      "  unreadable       0.75      0.75      0.75         4\n",
      "\n",
      "    accuracy                           0.80        10\n",
      "   macro avg       0.79      0.79      0.79        10\n",
      "weighted avg       0.80      0.80      0.80        10\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: title={'center': 'Confusion Matrix (Normalized)'}, xlabel='Predicted Classes', ylabel='Actual Classes'>"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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uDF69OgBR0dHtG7dGt27d39nr9DrmPAQERFpszLu4cnLy0NCQgICAgLEMqlUioCAAMTHxys9plGjRkhISBATnNu3b2PHjh1o27Ztkc/LSctERESksqysLIXX+vr60NfXL1AvIyMDMpkM1tbWCuXW1ta4evWq0rZ79OiBjIwMNGnSBIIg4OXLlxg8eDCHtIiIiKiI1DSkZW9vDzMzM3GLjIxUW4hxcXGYMWMGfvjhB5w+fRqbN2/G9u3bMX369CK3wR4eIiIibSaRqLgs/dWQVnJyMkxNTcViZb07AGBpaQkdHR2kpqYqlKempsLGxkbpMZMnT0bv3r3Rv39/AEC9evWQk5ODgQMHYuLEiZBK3x0/e3iIiIhIZaampgpbYQmPnp4evL29ERsbK5bJ5XLExsbCz89P6TFPnz4tkNTo6OgAAARBKFJ87OEhIiLSZlLJq02V44spLCwMwcHB8PHxQcOGDREVFYWcnByEhIQAAPr06QM7OztxWCwoKAjz589H/fr14evri5s3b2Ly5MkICgoSE593YcJDRESkzcrhTsvdunVDeno6wsPDkZKSAi8vL8TExIgTmZOSkhR6dCZNmgSJRIJJkybh3r17sLKyQlBQEL755puihykUtS+IKoysrCyYmZlBv94ASHT0yjscolLx6OT35R0CUanJysqCtYUZMjMzFebFqPscZmZm0G86CZJKBiVuR3j5HLmHvi7VWNWBPTxERETajA8PJSIiIo3Hh4cSERERaQb28BAREWkzDmkRERGRxtOSIS0mPERERNpMS3p4KkZaRkRERKQC9vAQERFpMw5pERERkcbjkBYRERGRZmAPDxERkVZTcUirgvSdMOEhIiLSZhzSIiIiItIM7OEhIiLSZhKJiqu0KkYPDxMeIiIibaYly9IrRpREREREKmAPDxERkTbTkknLTHiIiIi0mZYMaTHhISIi0mZa0sNTMdIyIiIiIhWwh4eIiEibcUiLiIiINB6HtIiIiIg0A3t4iIiItJhEIoFEC3p4mPAQERFpMW1JeDikRURERBqPPTxERETaTPLPpsrxFQATHiIiIi3GIS0iIiIiDcEeHiIiIi2mLT08THiIiIi0GBMeIiIi0njakvBwDg8RERFpPPbwEBERaTMuSyciIiJNxyEtIiIiIg3BHh4iIiItJpFAxR4e9cVSmpjwEBERaTEJVBzS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",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "true_labels = df[\"readable\"].map(CODE_READABILITY_PROMPT_RAILS_MAP).tolist()\n",
    "\n",
    "print(classification_report(true_labels, readability_classifications, labels=rails))\n",
    "confusion_matrix = ConfusionMatrix(\n",
    "    actual_vector=true_labels, predict_vector=readability_classifications, classes=rails\n",
    ")\n",
    "confusion_matrix.plot(\n",
    "    cmap=plt.colormaps[\"Blues\"],\n",
    "    number_label=True,\n",
    "    normalized=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspecting evaluations\n",
    "\n",
    "Because the evals are binary classifications, we can easily sample a few rows\n",
    "where the evals deviated from ground truth and see what the actual code was in\n",
    "that case."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Unnamed: 0</th>\n",
       "      <th>task_id</th>\n",
       "      <th>input</th>\n",
       "      <th>canonical_solution</th>\n",
       "      <th>test</th>\n",
       "      <th>entry_point</th>\n",
       "      <th>readable</th>\n",
       "      <th>output</th>\n",
       "      <th>readability</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>79</td>\n",
       "      <td>HumanEval/79</td>\n",
       "      <td>\\ndef decimal_to_binary(decimal):\\n    \"\"\"You will be given a number in decimal form and your task is to convert it to\\n    binary format. The function should return a string, with each character representing a binary\\n    number. Each character in the string will be '0' or '1'.\\n\\n    There will be an extra couple of characters 'db' at the beginning and at the end of the string.\\n    The extra characters are there to help with the format.\\n\\n    Examples:\\n    decimal_to_binary(15)   # returns \"db1111db\"\\n    decimal_to_binary(32)   # returns \"db100000db\"\\n    \"\"\"\\n</td>\n",
       "      <td>return \"db\" + bin(decimal)[2:] + \"db\"\\n</td>\n",
       "      <td>def check(candidate):\\n\\n    # Check some simple cases\\n    assert candidate(0) == \"db0db\"\\n    assert candidate(32) == \"db100000db\"\\n    assert candidate(103) == \"db1100111db\"\\n    assert candidate(15) == \"db1111db\", \"This prints if this assert fails 1 (good for debugging!)\"\\n\\n    # Check some edge cases that are easy to work out by hand.\\n    assert True, \"This prints if this assert fails 2 (also good for debugging!)\"\\n\\n</td>\n",
       "      <td>decimal_to_binary</td>\n",
       "      <td>False</td>\n",
       "      <td>def obscure_code(decimal):\\n    binary = bin(decimal)\\n    binary = binary[2:]\\n    prefix = \"db\"\\n    suffix = \"db\"\\n    result = prefix + binary + suffix\\n    return result\\n\\nprint(obscure_code(10))</td>\n",
       "      <td>readable</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   Unnamed: 0       task_id  \\\n",
       "0          79  HumanEval/79   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           input  \\\n",
       "0  \\ndef decimal_to_binary(decimal):\\n    \"\"\"You will be given a number in decimal form and your task is to convert it to\\n    binary format. The function should return a string, with each character representing a binary\\n    number. Each character in the string will be '0' or '1'.\\n\\n    There will be an extra couple of characters 'db' at the beginning and at the end of the string.\\n    The extra characters are there to help with the format.\\n\\n    Examples:\\n    decimal_to_binary(15)   # returns \"db1111db\"\\n    decimal_to_binary(32)   # returns \"db100000db\"\\n    \"\"\"\\n   \n",
       "\n",
       "                            canonical_solution  \\\n",
       "0      return \"db\" + bin(decimal)[2:] + \"db\"\\n   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                           test  \\\n",
       "0  def check(candidate):\\n\\n    # Check some simple cases\\n    assert candidate(0) == \"db0db\"\\n    assert candidate(32) == \"db100000db\"\\n    assert candidate(103) == \"db1100111db\"\\n    assert candidate(15) == \"db1111db\", \"This prints if this assert fails 1 (good for debugging!)\"\\n\\n    # Check some edge cases that are easy to work out by hand.\\n    assert True, \"This prints if this assert fails 2 (also good for debugging!)\"\\n\\n   \n",
       "\n",
       "         entry_point  readable  \\\n",
       "0  decimal_to_binary     False   \n",
       "\n",
       "                                                                                                                                                                                                      output  \\\n",
       "0  def obscure_code(decimal):\\n    binary = bin(decimal)\\n    binary = binary[2:]\\n    prefix = \"db\"\\n    suffix = \"db\"\\n    result = prefix + binary + suffix\\n    return result\\n\\nprint(obscure_code(10))   \n",
       "\n",
       "  readability  \n",
       "0    readable  "
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df[\"readability\"] = readability_classifications\n",
    "# inspect instances where ground truth was readable but evaluated to unreadable by the LLM\n",
    "filtered_df = df.query('readable == False and readability == \"readable\"')\n",
    "\n",
    "# inspect first 5 rows that meet this condition\n",
    "result = filtered_df.head(5)\n",
    "result"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Classifications with explanations\n",
    "\n",
    "When evaluating a dataset for readability, it can be useful to know why the LLM classified text as readable or not. The following code block runs `llm_classify` with explanations turned on so that we can inspect why the LLM made the classification it did. There is speed tradeoff since more tokens is being generated but it can be highly informative when troubleshooting."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Using prompt:\n",
      "\n",
      "\n",
      "You are a stern but practical senior software engineer who cares a lot about simplicity and\n",
      "readability of code. Can you review the following code that was written by another engineer?\n",
      "Focus on readability of the code. The implementation is \"readable\" if you think the code is\n",
      "readable, or \"unreadable\" if the code is unreadable or needlessly complex for what it's trying\n",
      "to accomplish.\n",
      "\n",
      "Task Assignment:\n",
      "```\n",
      "{input}\n",
      "```\n",
      "\n",
      "Implementation to Evaluate:\n",
      "```\n",
      "{output}\n",
      "```\n",
      "\n",
      "Please read the code carefully, then write out in a step by step manner an EXPLANATION to show how\n",
      "to evaluate the readability of the code. Avoid simply stating the correct answer at the outset.\n",
      "Your response LABEL must be a single word, either \"readable\" or \"unreadable\", and should not\n",
      "contain any text or characters aside from that. \"readable\" means that the code is readable.\n",
      "\"unreadable\" means the code is unreadable or needlessly complex for what it's trying to accomplish.\n",
      "\n",
      "Example response:\n",
      "************\n",
      "EXPLANATION: An explanation of your reasoning for why the label is \"readable\" or \"unreadable\"\n",
      "LABEL: \"readable\" or \"unreadable\"\n",
      "************\n",
      "\n",
      "EXPLANATION:\n",
      "OpenAI invocation parameters: {'model': 'gpt-4', 'temperature': 0.0, 'max_tokens': 256, 'frequency_penalty': 0, 'presence_penalty': 0, 'top_p': 1, 'n': 1, 'timeout': None}\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "e143e9cddf8c4140b0a846a1199ac4ff",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "llm_classify |          | 0/5 (0.0%) | ⏳ 00:00<? | ?it/s"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "- Snapped 'unreadable' to rail: unreadable\n",
      "- Snapped 'readable' to rail: readable\n",
      "- Snapped 'readable' to rail: readable\n",
      "- Snapped 'readable' to rail: readable\n",
      "- Snapped 'unreadable' to rail: unreadable\n"
     ]
    }
   ],
   "source": [
    "small_df_sample = df.copy().sample(n=5).reset_index(drop=True)\n",
    "readability_classifications_df = llm_classify(\n",
    "    dataframe=small_df_sample,\n",
    "    template=CODE_READABILITY_PROMPT_TEMPLATE,\n",
    "    model=model,\n",
    "    rails=rails,\n",
    "    provide_explanation=True,\n",
    "    verbose=True,\n",
    "    concurrency=20,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>input</th>\n",
       "      <th>output</th>\n",
       "      <th>label</th>\n",
       "      <th>explanation</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>\\ndef decimal_to_binary(decimal):\\n    \"\"\"You will be given a number in decimal form and your task is to convert it to\\n    binary format. The function should return a string, with each character representing a binary\\n    number. Each character in the string will be '0' or '1'.\\n\\n    There will be an extra couple of characters 'db' at the beginning and at the end of the string.\\n    The extra characters are there to help with the format.\\n\\n    Examples:\\n    decimal_to_binary(15)   # returns \"db1111db\"\\n    decimal_to_binary(32)   # returns \"db100000db\"\\n    \"\"\"\\n</td>\n",
       "      <td>def obscure_code(decimal):\\n    binary = bin(decimal)\\n    binary = binary[2:]\\n    prefix = \"db\"\\n    suffix = \"db\"\\n    result = prefix + binary + suffix\\n    return result\\n\\nprint(obscure_code(10))</td>\n",
       "      <td>readable</td>\n",
       "      <td>The code is quite straightforward and easy to understand. It starts by converting the decimal number to binary using the built-in bin() function. The result of this function is a string that starts with '0b', so the next line removes the first two characters. Then, it defines a prefix and a suffix, both 'db', and concatenates them with the binary string. The final result is returned. The function name 'obscure_code' is not very descriptive and does not match the task assignment 'decimal_to_binary', which could lead to confusion. However, the code inside the function is simple and readable.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>\\ndef words_in_sentence(sentence):\\n    \"\"\"\\n    You are given a string representing a sentence,\\n    the sentence contains some words separated by a space,\\n    and you have to return a string that contains the words from the original sentence,\\n    whose lengths are prime numbers,\\n    the order of the words in the new string should be the same as the original one.\\n\\n    Example 1:\\n        Input: sentence = \"This is a test\"\\n        Output: \"is\"\\n\\n    Example 2:\\n        Input: sentence = \"lets go for swimming\"\\n        Output: \"go for\"\\n\\n    Constraints:\\n        * 1 &lt;= len(sentence) &lt;= 100\\n        * sentence contains only letters\\n    \"\"\"\\n</td>\n",
       "      <td>new_lst = []\\n    for word in sentence.split():\\n        flg = 0\\n        if len(word) == 1:\\n            flg = 1\\n        for i in range(2, len(word)):\\n            if len(word)%i == 0:\\n                flg = 1\\n        if flg == 0 or len(word) == 2:\\n            new_lst.append(word)\\n    return \" \".join(new_lst)\\n</td>\n",
       "      <td>unreadable</td>\n",
       "      <td>The code is relatively simple and straightforward, but there are a few areas where it could be improved for readability. The variable names 'new_lst' and 'flg' are not very descriptive, which can make it harder to understand what the code is doing. The logic for checking if a word's length is a prime number is also a bit convoluted and could be simplified or at least commented for clarity. The code lacks function definition and indentation, which are crucial for readability and understanding the flow of the code. The code also lacks error handling and doesn't check if the input is valid according to the constraints mentioned in the task assignment. Overall, while the code is not completely unreadable, it could definitely be improved for better readability.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>\\ndef prime_length(string):\\n    \"\"\"Write a function that takes a string and returns True if the string\\n    length is a prime number or False otherwise\\n    Examples\\n    prime_length('Hello') == True\\n    prime_length('abcdcba') == True\\n    prime_length('kittens') == True\\n    prime_length('orange') == False\\n    \"\"\"\\n</td>\n",
       "      <td>l = len(string)\\n    if l == 0 or l == 1:\\n        return False\\n    for i in range(2, l):\\n        if l % i == 0:\\n            return False\\n    return True\\n</td>\n",
       "      <td>readable</td>\n",
       "      <td>The code is quite straightforward and easy to understand. It first calculates the length of the string. Then it checks if the length is 0 or 1, in which case it returns False as neither 0 nor 1 are prime numbers. After that, it enters a loop where it checks if the length of the string is divisible by any number in the range from 2 to the length of the string (exclusive). If it finds a number that divides the length, it returns False as it means the length is not a prime number. If it doesn't find any such number, it returns True, indicating that the length is a prime number. The code is simple and does not contain any unnecessary complexity. The variable names could be more descriptive, but overall, the code is readable.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>from typing import List, Tuple\\n\\n\\ndef rolling_max(numbers: List[int]) -&gt; List[int]:\\n    \"\"\" From a given list of integers, generate a list of rolling maximum element found until given moment\\n    in the sequence.\\n    &gt;&gt;&gt; rolling_max([1, 2, 3, 2, 3, 4, 2])\\n    [1, 2, 3, 3, 3, 4, 4]\\n    \"\"\"\\n</td>\n",
       "      <td>running_max = None\\n    result = []\\n\\n    for n in numbers:\\n        if running_max is None:\\n            running_max = n\\n        else:\\n            running_max = max(running_max, n)\\n\\n        result.append(running_max)\\n\\n    return result\\n</td>\n",
       "      <td>readable</td>\n",
       "      <td>The code is well-structured and follows a logical flow. It starts by initializing a variable 'running_max' to None and an empty list 'result'. It then iterates over the list of numbers. For each number, it checks if 'running_max' is None (which it will be for the first number), and if so, sets 'running_max' to that number. Otherwise, it sets 'running_max' to the maximum of 'running_max' and the current number. It then appends 'running_max' to the 'result' list. Finally, it returns the 'result' list. The code is simple and straightforward, making it easy to understand what it's doing. The function name and docstring also clearly describe what the function does, which aids in readability.</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>\\n\\ndef derivative(xs: list):\\n    \"\"\" xs represent coefficients of a polynomial.\\n    xs[0] + xs[1] * x + xs[2] * x^2 + ....\\n     Return derivative of this polynomial in the same form.\\n    &gt;&gt;&gt; derivative([3, 1, 2, 4, 5])\\n    [1, 4, 12, 20]\\n    &gt;&gt;&gt; derivative([1, 2, 3])\\n    [2, 6]\\n    \"\"\"\\n</td>\n",
       "      <td>return list(map(lambda pair: pair[0] * pair[1], list(filter(lambda pair: pair[0] != 0, list(zip(list(range(len(xs))), xs))))))</td>\n",
       "      <td>unreadable</td>\n",
       "      <td>The code is trying to calculate the derivative of a polynomial. It does this by creating a list of tuples where each tuple contains the index and the corresponding value from the input list. It then filters out the tuples where the index is 0, because the derivative of a constant term is 0. Finally, it uses map to multiply each index with its corresponding value, which gives the derivative of each term in the polynomial. While the logic is correct, the implementation is quite complex and hard to read. It uses a lot of nested functions and list comprehensions, which makes it difficult to understand what each part of the code is doing. A more readable implementation would break this down into multiple steps and use more descriptive variable names.</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               input  \\\n",
       "0                                                                                      \\ndef decimal_to_binary(decimal):\\n    \"\"\"You will be given a number in decimal form and your task is to convert it to\\n    binary format. The function should return a string, with each character representing a binary\\n    number. Each character in the string will be '0' or '1'.\\n\\n    There will be an extra couple of characters 'db' at the beginning and at the end of the string.\\n    The extra characters are there to help with the format.\\n\\n    Examples:\\n    decimal_to_binary(15)   # returns \"db1111db\"\\n    decimal_to_binary(32)   # returns \"db100000db\"\\n    \"\"\"\\n   \n",
       "1  \\ndef words_in_sentence(sentence):\\n    \"\"\"\\n    You are given a string representing a sentence,\\n    the sentence contains some words separated by a space,\\n    and you have to return a string that contains the words from the original sentence,\\n    whose lengths are prime numbers,\\n    the order of the words in the new string should be the same as the original one.\\n\\n    Example 1:\\n        Input: sentence = \"This is a test\"\\n        Output: \"is\"\\n\\n    Example 2:\\n        Input: sentence = \"lets go for swimming\"\\n        Output: \"go for\"\\n\\n    Constraints:\\n        * 1 <= len(sentence) <= 100\\n        * sentence contains only letters\\n    \"\"\"\\n   \n",
       "2                                                                                                                                                                                                                                                                                                                                                \\ndef prime_length(string):\\n    \"\"\"Write a function that takes a string and returns True if the string\\n    length is a prime number or False otherwise\\n    Examples\\n    prime_length('Hello') == True\\n    prime_length('abcdcba') == True\\n    prime_length('kittens') == True\\n    prime_length('orange') == False\\n    \"\"\"\\n   \n",
       "3                                                                                                                                                                                                                                                                                                                                                                          from typing import List, Tuple\\n\\n\\ndef rolling_max(numbers: List[int]) -> List[int]:\\n    \"\"\" From a given list of integers, generate a list of rolling maximum element found until given moment\\n    in the sequence.\\n    >>> rolling_max([1, 2, 3, 2, 3, 4, 2])\\n    [1, 2, 3, 3, 3, 4, 4]\\n    \"\"\"\\n   \n",
       "4                                                                                                                                                                                                                                                                                                                                                                          \\n\\ndef derivative(xs: list):\\n    \"\"\" xs represent coefficients of a polynomial.\\n    xs[0] + xs[1] * x + xs[2] * x^2 + ....\\n     Return derivative of this polynomial in the same form.\\n    >>> derivative([3, 1, 2, 4, 5])\\n    [1, 4, 12, 20]\\n    >>> derivative([1, 2, 3])\\n    [2, 6]\\n    \"\"\"\\n   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                              output  \\\n",
       "0                                                                                                                          def obscure_code(decimal):\\n    binary = bin(decimal)\\n    binary = binary[2:]\\n    prefix = \"db\"\\n    suffix = \"db\"\\n    result = prefix + binary + suffix\\n    return result\\n\\nprint(obscure_code(10))   \n",
       "1      new_lst = []\\n    for word in sentence.split():\\n        flg = 0\\n        if len(word) == 1:\\n            flg = 1\\n        for i in range(2, len(word)):\\n            if len(word)%i == 0:\\n                flg = 1\\n        if flg == 0 or len(word) == 2:\\n            new_lst.append(word)\\n    return \" \".join(new_lst)\\n   \n",
       "2                                                                                                                                                                    l = len(string)\\n    if l == 0 or l == 1:\\n        return False\\n    for i in range(2, l):\\n        if l % i == 0:\\n            return False\\n    return True\\n   \n",
       "3                                                                              running_max = None\\n    result = []\\n\\n    for n in numbers:\\n        if running_max is None:\\n            running_max = n\\n        else:\\n            running_max = max(running_max, n)\\n\\n        result.append(running_max)\\n\\n    return result\\n   \n",
       "4                                                                                                                                                                                                     return list(map(lambda pair: pair[0] * pair[1], list(filter(lambda pair: pair[0] != 0, list(zip(list(range(len(xs))), xs))))))   \n",
       "\n",
       "        label  \\\n",
       "0    readable   \n",
       "1  unreadable   \n",
       "2    readable   \n",
       "3    readable   \n",
       "4  unreadable   \n",
       "\n",
       "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      explanation  \n",
       "0                                                                                                                                                                            The code is quite straightforward and easy to understand. It starts by converting the decimal number to binary using the built-in bin() function. The result of this function is a string that starts with '0b', so the next line removes the first two characters. Then, it defines a prefix and a suffix, both 'db', and concatenates them with the binary string. The final result is returned. The function name 'obscure_code' is not very descriptive and does not match the task assignment 'decimal_to_binary', which could lead to confusion. However, the code inside the function is simple and readable.  \n",
       "1  The code is relatively simple and straightforward, but there are a few areas where it could be improved for readability. The variable names 'new_lst' and 'flg' are not very descriptive, which can make it harder to understand what the code is doing. The logic for checking if a word's length is a prime number is also a bit convoluted and could be simplified or at least commented for clarity. The code lacks function definition and indentation, which are crucial for readability and understanding the flow of the code. The code also lacks error handling and doesn't check if the input is valid according to the constraints mentioned in the task assignment. Overall, while the code is not completely unreadable, it could definitely be improved for better readability.  \n",
       "2                                      The code is quite straightforward and easy to understand. It first calculates the length of the string. Then it checks if the length is 0 or 1, in which case it returns False as neither 0 nor 1 are prime numbers. After that, it enters a loop where it checks if the length of the string is divisible by any number in the range from 2 to the length of the string (exclusive). If it finds a number that divides the length, it returns False as it means the length is not a prime number. If it doesn't find any such number, it returns True, indicating that the length is a prime number. The code is simple and does not contain any unnecessary complexity. The variable names could be more descriptive, but overall, the code is readable.  \n",
       "3                                                                         The code is well-structured and follows a logical flow. It starts by initializing a variable 'running_max' to None and an empty list 'result'. It then iterates over the list of numbers. For each number, it checks if 'running_max' is None (which it will be for the first number), and if so, sets 'running_max' to that number. Otherwise, it sets 'running_max' to the maximum of 'running_max' and the current number. It then appends 'running_max' to the 'result' list. Finally, it returns the 'result' list. The code is simple and straightforward, making it easy to understand what it's doing. The function name and docstring also clearly describe what the function does, which aids in readability.  \n",
       "4             The code is trying to calculate the derivative of a polynomial. It does this by creating a list of tuples where each tuple contains the index and the corresponding value from the input list. It then filters out the tuples where the index is 0, because the derivative of a constant term is 0. Finally, it uses map to multiply each index with its corresponding value, which gives the derivative of each term in the polynomial. While the logic is correct, the implementation is quite complex and hard to read. It uses a lot of nested functions and list comprehensions, which makes it difficult to understand what each part of the code is doing. A more readable implementation would break this down into multiple steps and use more descriptive variable names.  "
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# Let's view the data\n",
    "merged_df = pd.merge(\n",
    "    small_df_sample, readability_classifications_df, left_index=True, right_index=True\n",
    ")\n",
    "merged_df[[\"input\", \"output\", \"label\", \"explanation\"]].head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## LLM Evals: Code Readability Classifications GPT-3.5\n",
    "\n",
    "Run readability classifications against a subset of the data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "1f2caf9e17394c858ff61686af403cbd",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "llm_classify |          | 0/10 (0.0%) | ⏳ 00:00<? | ?it/s"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# The rails is used to hold the output to specific values based on the template\n",
    "# It will remove text such as \",,,\" or \"...\"\n",
    "# Will ensure the binary value expected from the template is returned\n",
    "rails = list(CODE_READABILITY_PROMPT_RAILS_MAP.values())\n",
    "readability_classifications = llm_classify(\n",
    "    dataframe=df,\n",
    "    template=CODE_READABILITY_PROMPT_TEMPLATE,\n",
    "    model=OpenAIModel(model=\"gpt-3.5-turbo\", temperature=0.0),\n",
    "    rails=rails,\n",
    "    concurrency=20,\n",
    ")[\"label\"].tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "              precision    recall  f1-score   support\n",
      "\n",
      "    readable       0.86      1.00      0.92         6\n",
      "  unreadable       1.00      0.75      0.86         4\n",
      "\n",
      "    accuracy                           0.90        10\n",
      "   macro avg       0.93      0.88      0.89        10\n",
      "weighted avg       0.91      0.90      0.90        10\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: title={'center': 'Confusion Matrix (Normalized)'}, xlabel='Predicted Classes', ylabel='Actual Classes'>"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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lSv49ttvsXnzZowZM0at8zLhISIionJz+vRpeHt7w9vbGwAQHh4Ob29vREREAADu3bsnT34AoHbt2ti1axf2798PLy8vzJs3DytXrkRgYKBa5+WQFhERkT4TiTQc0lLvWH9/f7zpFoDK7qLs7++Ps2fPqhuZAiY8RERE+qwUw1LFjq8EKkeURERERBpgDw8REZE+K+chrYrChIeIiEivaTikVUkGiypHlEREREQaYA8PERGRPuOQFhEREek8rtIiIiIi0g3s4SEiItJnHNIiIiIinacnQ1pMeIiIiPSZnvTwVI60jIiIiEgD7OEhIiLSZxzSIiIiIp0nEmmY8HBIi4iIiOg/gT08RERE+kwserlpcnwlwISHiIhIn+nJHJ7KESURERGRBtjDQ0REpM/05D48THiIiIj0GYe0iIiIiHQDe3iIiIj0GYe0iIiISOfpyZAWEx4iIiJ9pic9PJUjLSMiIiLSAHt4iIiI9BmHtIiIiEjncUiLiIiISDewh4eIiEivaTikVUn6TpjwEBER6TMOaRERERHpBvbwEBER6TORSMNVWpWjh4cJDxERkT7Tk2XplSNKIiIiIg2wh4eIiEif6cmkZSY8RERE+kxPhrSY8BAREekzPenhqRxpGREREZEG2MNDRESkzzikRURERDqPQ1pEREREuoE9PERERHpMJBJBpAc9PEx4iIiI9Ji+JDwc0iIiIiKdxx4eIiIifSb6/02T4ysBJjxERER6jENaRERERDqCPTxERER6TF96eJjwEBER6TEmPERERKTz9CXh4RweIiIi0nns4SEiItJnXJZOREREuo5DWkREREQ6gj08REREekwkgoY9PNqLpSwx4SEiItJjImg4pFVJMh4OaREREZHOYw8PERGRHtOXSctMeIiIiPSZnixL55AWERER6Tz28BAREekzDYe0BA5pERER0X+dpnN4NFvhVX6Y8BAREekxfUl4OIeHiIiIyt2SJUvg6uoKY2Nj+Pj44OTJk2+sHxcXh/r168PExATOzs4YM2YMnj17pvL5mPAQERHpM5EWNjVt2rQJ4eHhiIyMxJkzZ+Dl5YXAwEBkZmYqrb9+/Xp8+eWXiIyMxOXLl/H9999j06ZNmDx5ssrnZMJDRESkx4qGtDTZ1DV//nwMHjwYAwYMQKNGjbBs2TKYmppi1apVSusfP34crVq1Qp8+feDq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SEpKwp07d7Bv3z5cv34dDRs2BABERETgxx9/RHR0NP78809cvnwZGzduxNSpUwEAq1evxvfff4+LFy/i1q1b+Omnn2BiYgIXFxeVvqdEVIKKnkRERNrx6qRldfbfu3dP6Nevn2BraytIJBKhTp06wuDBg4WcnBxBEF5OUh41apRgaWkpWFtbC+Hh4UK/fv1KnLQsCILw9OlTYcyYMYKjo6NgZGQkuLm5CatWrZLvnz59uuDg4CCIRCIhNDRUEISXE63j4uKE+vXrC4aGhoKdnZ0QGBgoHDp0SH7cr7/+Kri5uQkSiURo06aNsGrVqrdONhYEQTh16pTQo0cPwc7OTpBIJIKbm5swZMgQ4fr164IgFJ+0nJ2dLXTr1k0wNzcXqlevLkydOlXhmi9duiQEBgbK26tXr56wePFiQRAEIT09XQgODpZfu4uLixARESFIpVJ5PPHx8ULLli0FExMTwdLSUmjRooWwfPlyQRAEYfv27YKPj49gaWkpmJmZCe+9955w4MCBN14fEb2dSBBKOauPiIiIqJLgkBYRERHpPCY8REREpPOY8BAREZHOY8JDREREOo8JDxEREek8JjxERESk85jwEBERkc5jwkNEREQ6jwkPERER6TwmPERERKTzmPAQERGRzmPCQ0RERDrv/wAQjHFdFcKOpgAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "true_labels = df[\"readable\"].map(CODE_READABILITY_PROMPT_RAILS_MAP).tolist()\n",
    "\n",
    "print(classification_report(true_labels, readability_classifications, labels=rails))\n",
    "confusion_matrix = ConfusionMatrix(\n",
    "    actual_vector=true_labels, predict_vector=readability_classifications, classes=rails\n",
    ")\n",
    "confusion_matrix.plot(\n",
    "    cmap=plt.colormaps[\"Blues\"],\n",
    "    number_label=True,\n",
    "    normalized=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Preview: GPT-4 Turbo"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "ea005c3cf9cf4ba7ba46735a913bc986",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "llm_classify |          | 0/10 (0.0%) | ⏳ 00:00<? | ?it/s"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "rails = list(CODE_READABILITY_PROMPT_RAILS_MAP.values())\n",
    "readability_classifications = llm_classify(\n",
    "    dataframe=df,\n",
    "    template=CODE_READABILITY_PROMPT_TEMPLATE,\n",
    "    model=OpenAIModel(model=\"gpt-4-turbo-preview\", temperature=0.0),\n",
    "    rails=rails,\n",
    "    concurrency=20,\n",
    ")[\"label\"].tolist()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "              precision    recall  f1-score   support\n",
      "\n",
      "    readable       0.80      0.67      0.73         6\n",
      "  unreadable       0.60      0.75      0.67         4\n",
      "\n",
      "    accuracy                           0.70        10\n",
      "   macro avg       0.70      0.71      0.70        10\n",
      "weighted avg       0.72      0.70      0.70        10\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Axes: title={'center': 'Confusion Matrix (Normalized)'}, xlabel='Predicted Classes', ylabel='Actual Classes'>"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "true_labels = df[\"readable\"].map(CODE_READABILITY_PROMPT_RAILS_MAP).tolist()\n",
    "\n",
    "print(classification_report(true_labels, readability_classifications, labels=rails))\n",
    "confusion_matrix = ConfusionMatrix(\n",
    "    actual_vector=true_labels, predict_vector=readability_classifications, classes=rails\n",
    ")\n",
    "confusion_matrix.plot(\n",
    "    cmap=plt.colormaps[\"Blues\"],\n",
    "    number_label=True,\n",
    "    normalized=True,\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
